#!/usr/bin/env ruby1.8
#
# Copyright (c) 2006-2010 National ICT Australia (NICTA), Australia
#
# Copyright (c) 2004-2009 WINLAB, Rutgers University, USA
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
#

#-------------------------------------------------------------#
#---- The functionalities of this file were enhanced by ------#
#------- LABORA - Federal University of Goiás (UFG). ---------#
#-------------------------------------------------------------#

require 'optparse'

@version="-5.4"
@ec_path="/usr/share/omf-expctl#{@version}/"
@gempath="/usr/share/omf-common#{@version}/gems/1.8:/usr/share/omf-expctl#{@version}/gems/1.8"
@this_app=File.basename(__FILE__)

def parseOptions()
  @options = {}
  
  opts = OptionParser.new do |opts|
    @options[:help] = false
    opts.on( '-h', '--help', "Show help" ) do
      @options[:help] = true
    end
    @options[:debug] = false
    opts.on( '-d', '--debug', "Debug (this script only, not passed to omf exec)" ) do
      @options[:debug] = true
    end
    @options[:summary] = false
    opts.on( '-s', '--summary', "Show stat summary" ) do
      @options[:summary] = true
    end
    @options[:aggregate] = ""
    opts.on( '-c', '--config AGGREGATE', "Aggregate Name" ) do|c|
      @options[:aggregate] = c
    end
    @options[:action] = ""
    opts.on( '-a', '--action ACTION', "CMC action (on, offs, offh)" ) do|a|
      @options[:action] = a
    end
    @options[:topology] = "system:topo:all"
    opts.on( '-t', '--topology TOPOLOGY', "Topology" ) do|t|
      if t == "all"
        @options[:topology] = "system:topo:all"
      else
        @options[:topology] = t
      end
    end
    @options[:image] = "baseline.ndz"
    opts.on( '-i', '--image IMAGE', "Disk image to load" ) do|i|
      @options[:image] = i
    end
    @options[:timeout] = "800"
    opts.on( '-o', '--timeout SECONDS', "Load image timeout" ) do|o|
      @options[:timeout] = o
    end
    @options[:node] = ""
    opts.on( '-n', '--node NODE', "Save image node" ) do|n|
      @options[:node] = n
    end
    @options[:outpath] = nil
    opts.on( '', '--outpath PATH', "Path where the resulting Topologies should be saved" ) do|p|
      @options[:outpath] = p
    end
    @options[:outprefix] = nil
    opts.on( '', '--outprefix PREFIX', "Prefix to use for naming the resulting Topologies" ) do|p|
      @options[:outprefix] = p
    end
    opts.on( '-r', '--resize SIZE', "Resize the disk image to SIZE GB or leave SIZE % free space" ) do|r|
      @options[:resize] = r
    end
  end

  begin
    # do not parse options for exec, since it uses its own option parser
    opts.parse!(Array.new(ARGV)) if (ARGV[0]!="exec")    
  rescue OptionParser::InvalidOption, OptionParser::MissingArgument
    puts $!.to_s
    help(ARGV[0])
    exit 1
  end
  #p @options
end

def run(cmd)

  #-------------------------------------------------------------#
  #--- Verify if nodes used were reserved - by LABORA - UFG ----#
  #-------------------------------------------------------------#

  require 'mysql'
  require 'etc'
  username = Etc.getlogin()

  time = Time.new
  current_time = time.strftime("%Y-%m-%d %H:%M:%S")

  reserved_nodes = Array.new
  all_nodes = Array.new

  # Islands loop begin
  con_noc = Mysql.new("localhost", "omfn", "omfn", "scheduler")
  query = "SELECT ip_address, scheduler_db, inventory_db FROM islands"
  rs = con_noc.query(query)
  rs.each_hash do |i|

    con_ilha = Mysql.new(i['ip_address'], "omfn", "omfn", i['scheduler_db'])

    # Search for reserved nodes
    query = "SELECT hrn FROM #{i['inventory_db']}.nodes AS n, " +
            "#{i['scheduler_db']}.reservation AS r WHERE r.node_id=n.id " +
            "AND r.begin_time<'#{current_time}' AND r.end_time>'#{current_time}' AND r.username='#{username}'"
    rs = con_ilha.query(query)
    rs.each_hash do |n|
      reserved_nodes.push(n['hrn'])
    end

    # Search for all nodes
    query = "SELECT hrn FROM #{i['inventory_db']}.nodes"
    rs = con_ilha.query(query)
    rs.each_hash do |n|
      all_nodes.push(n['hrn'])
    end

    con_ilha.close()

  end
  # Islands loop end.

  con_noc.close()

  if (cmd == "exec")
    single_params = ["-a","--allow-missing","-d","--debug","-i","--interactive","-n","--just-print","-N","--no-am","-O","--output-app","-r","--reset","-s","--shutdown","-h","--help","-v","--version"]
    double_params = ["-c","--config" "-C","--configfile","-l","--libraries","--log","-m","--message","-p","--print","-o","--output-result","-e","--experiment-id","-S","--slice","-t","--tags","--oml-uri","-x","--extra-libs","--slave-mode","--slave-mode-resource"]

    params = Array.new(ARGV)
    params.shift	

    filename = ""
    params.each { |p|
      if single_params.include?(p)
      elsif double_params.include?(p)
        params.shift
      else
        filename = p
        break
      end
    }

    script_nodes = Array.new

    if filename != "--" and File.exists?(filename)
      file = File.open(filename,"r").read
      file.each_line do |line|
        if not line.start_with?("#")
          if line.include?("defGroup")
            splitted_quotes = line.split(/"|'/)
            splitted_comma = splitted_quotes[3].split(/,/)
            script_nodes += splitted_comma
          end
          if line.include?(".net.e0.ip")
            puts "Experiment failed."
            puts "Your script uses eth0 (alias e0)."
            puts "The interface eth0 is used only for control purpose."
            puts "Please, check your experiment description script."
          end
        end
      end
    end

    if script_nodes - reserved_nodes != []
      puts "Your script uses one or more unreserved nodes."
      puts "The nodes you have reserved:"
      puts reserved_nodes
      puts "The nodes your script is using:"
      puts script_nodes
      exit 1
    end

  elsif (["tell","stat","load","save"].include?(cmd))

    command_nodes = Array.new

    if cmd == "save"
      command_nodes.push(@options[:node])
    else
      if @options[:topology] == "system:topo:all"
        command_nodes = all_nodes
      else
        command_nodes = @options[:topology].split(/,/)
      end
    end

    if command_nodes - reserved_nodes != []
      puts "Your command uses one or more unreserved nodes."
      puts "The nodes you have reserved:"
      puts reserved_nodes
      puts "The nodes your command is using:"
      puts command_nodes
      exit 1
    end

  end

#-------------------------------------------------------------#
#-------------------------------------------------------------#
#-------------------------------------------------------------#

  args=""

  ########## OMF EXEC    
  if (cmd == "exec")
    app="omf-expctl.rb"
    args=Array.new(ARGV)
    args.delete(cmd)
    
  ########## OMF TELL    
  elsif (cmd == "tell")
    app="omf-expctl.rb"
    # needs an action
    if @options[:action].empty?
      help(cmd)
      exit 1
    end
    args=["system:exp:tell"]
    if !@options[:aggregate].empty?
      args << "-c '#{@options[:aggregate]}'"
    end
    args << "-- --command '#{@options[:action]}'"
    args << "--nodes '#{@options[:topology]}'"
    
  ########## OMF STAT
  elsif (cmd == "stat")
    app="omf-expctl.rb"
    args=["system:exp:stat"]
    if !@options[:aggregate].empty?
      args << "-c '#{@options[:aggregate]}'"
    end
    args << "-- --nodes '#{@options[:topology]}'"
    if @options[:summary]
      args << ["--summary true"]
    end
  ########## OMF LOAD
  elsif (cmd == "load")
    app="omf-expctl.rb"
    args=["-a -r -S pxe_slice -s system:exp:imageNode", "--",
          "--nodes '#{@options[:topology]}'", 
          "--image '#{@options[:image]}'", 
          "--timeout '#{@options[:timeout]}'"]
    args << "--outpath '#{@options[:outpath]}'" if @options[:outpath]
    args << "--outprefix '#{@options[:outprefix]}'" if @options[:outprefix]
    args << "--resize '#{@options[:resize]}'" if @options[:resize]
    if !@options[:aggregate].empty?
      args << "--domain '#{@options[:aggregate]}'"
    end
    
  ########## OMF SAVE
  elsif (cmd == "save")
    app="omf-expctl.rb"
    if @options[:node].empty?
      help(cmd)
      exit 1
    end
    args=["-r -S pxe_slice -s system:exp:saveNode", "--", "--node '#{@options[:node]}'"]
    args << "--resize '#{@options[:resize]}'" if @options[:resize]
    if !@options[:aggregate].empty?
      args << "--domain '#{@options[:aggregate]}'"
    end
  
  else
    # should never get here
    puts "Unknown command #{cmd}."
    help()
    exit 1
  end
  
  if !File.exists?("#{@ec_path}#{app}")
     puts "Cannot find the ruby module '#{@ec_path}#{app}'."
     exit 1
  end

  omf_command = "/usr/bin/env ruby1.8 -I#{@ec_path} -I/usr/share/omf-common#{@version} #{@ec_path}#{app} #{args.join(' ')}"
  puts "DEBUG: Executing '#{omf_command}'" if @options[:debug]
  ENV['GEM_PATH'] = @gempath
  exec omf_command
end

# display help for command 'cmd'
def help(cmd = nil)
  if (cmd == "exec")
    # exec has its own help
    ARGV << "-h"
    run(cmd)
  elsif (cmd == "stat")
    puts "Returns the status of the nodes in a testbed"
  	puts "Usage:"
  	puts "      #{@this_app} #{cmd} [-h] [-s] [-t TOPOLOGY] [-c AGGREGATE]"
  	puts " "
  	puts "      With: "
  	puts "      -h, --help                print this help message"
  	puts "      -s, --summary             print a summary of the node status for the testbed"
  	puts "      -c, --config AGGREGATE    use testbed AGGREGATE"
  	puts "      -t, --topology TOPOLOGY   a valid topology file or description (defaults to 'system:topo:all')"
  	puts " "
  	puts "      Some Examples: " 
  	puts "                    #{@this_app} #{cmd}"
  	puts "                    #{@this_app} #{cmd} -s"
  	puts "                    #{@this_app} #{cmd} -t omf.nicta.node1,omf.nicta.node2 -c sb1"
  	puts "                    #{@this_app} #{cmd} -t system:topo:all -c grid"
  	puts " "
  elsif (cmd == "tell")
    puts "Switch ON/OFF and reboot the nodes in a testbed"
  	puts "Usage:"
  	puts "      #{@this_app} #{cmd} -a ACTION [-h] [-t TOPOLOGY] [-c AGGREGATE]"
  	puts " "
  	puts "      With: "
  	puts "      -h, --help           print this help message"
  	puts " "
  	puts "      -a, --action ACTION  specify an action"  	
  	puts "      ACTION:"
  	puts "      on              turn node(s) ON"
  	puts "      offs            turn node(s) OFF (soft)"
  	puts "      offh            turn node(s) OFF (hard)"
  	puts "      reboot          reboots node(s) (soft)"
  	puts "      reset           resets node(s) (hard)"
  	puts " "
  	puts "      -c, --config AGGREGATE    use testbed AGGREGATE"
  	puts "      -t, --topology TOPOLOGY   a valid topology file or description (defaults to 'system:topo:all')"
  	puts " "
  	puts "      Some Examples: " 
  	puts "                    #{@this_app} #{cmd} -a reset"
  	puts "                    #{@this_app} #{cmd} -a on -t system:topo:all -c grid"
  	puts "                    #{@this_app} #{cmd} -a reboot -t omf.nicta.node1"
  	puts "                    #{@this_app} #{cmd} -a offs -t omf.nicta.node1,omf.nicta.node2 -c sb1"
  	puts "                    #{@this_app} #{cmd} -a offh -t system:topo:all"
  	puts "                    #{@this_app} #{cmd} -a reset -t topo_grid_active"
  	puts " "
  elsif (cmd == "load")
    puts "Install a given disk image on the nodes in a testbed"
    puts "Usage:"
    puts "      #{@this_app} #{cmd} [-h] [-i IMAGE_PATH] [-o TIMEOUT] [-t TOPOLOGY] [-c AGGREGATE]"
    puts " "
    puts "      With: "
    puts "      -h, --help                print this help message"
    puts "      -c, --config AGGREGATE    use testbed AGGREGATE"
    puts "      -t, --topology TOPOLOGY   a valid topology file or description (defaults to 'system:topo:all')"
    puts "                                (if a file 'TOPOLOGY' doesn't exist, interpret it as a comma-separated list of nodes)"
    puts "      -i, --image IMAGE         disk image to load"
    puts "                                (default is 'baseline.ndz', the latest stable baseline image)"
    puts "      -o, --timeout TIMEOUT     a duration (in sec.) after which imageNodes should stop waiting for"
    puts "                                nodes that have not finished their image installation"
    puts "                                (default is 800 sec, i.e. 13min 20sec)"
    puts "      -r, --resize SIZE         Resizes the first partition to SIZE GB or to maximum size if SIZE=0 or"
    puts "                                leave x percent of free space if SIZE=x%"    
    puts "          --outpath PATH        Path where the resulting Topologies should be saved" 
    puts "                                (default is '/tmp')"
    puts "          --outprefix PREFIX    Prefix to use for naming the resulting Topologies" 
    puts "                                (default is your experiment ID)"
    puts " "
    puts "      Some Examples: " 
    puts "                    #{@this_app} #{cmd}"
    puts "                    #{@this_app} #{cmd} -t system:topo:all -i baseline-2.4.ndz"
    puts "                    #{@this_app} #{cmd} -t omf.nicta.node1 -i wireless-2.6.ndz"
    puts "                    #{@this_app} #{cmd} -t omf.nicta.node1,omf.nicta.node2 -i baseline.ndz -o 400"
    puts "                    #{@this_app} #{cmd} -t system:topo:circle -i my_Own_Image.ndz"
    puts "                    #{@this_app} #{cmd} -t my_Own_Topology -i baseline-2.2.ndz -t 600 -c grid"
    puts "                    #{@this_app} #{cmd} -t my_Own_Topology --outpath ./ --outprefix my_Own_Prefix"
    puts " "
  elsif (cmd == "save")
    puts "Save a disk image from a given node into an archive file"
    puts "Usage:"
    puts "      #{@this_app} #{cmd} -n NODE [-h] [-c AGGREGATE]"
    puts " "
    puts "      With: "
    puts "      -h, --help          print this help message"
    puts "      -n, --node NODE     a valid description of a single node"
    puts "                          (no default here, you have to enter a node!)"
    puts "      -r, --resize SIZE   Resizes the first partition to SIZE GB or to maximum size if SIZE=0 or"
    puts "                          leave x percent of free space if SIZE=x%"        
    puts " "
    puts "      Some Examples: " 
    puts "                    #{@this_app} #{cmd} -n omf.nicta.node1"
    puts "                    #{@this_app} #{cmd} -n omf.nicta.node3 -c grid"
    puts " "
  else
    # generic help
    puts "Run a command on the testbed(s)"
    puts "Usage: #{@this_app} #{cmd} [COMMAND] [ARGUMENT]..."
    puts "  Available COMMANDs:"
    puts "    help   Print this help message or a specify command usage"
    puts "    exec   Execute an experiment script"
    puts "    load   Load a disk image on a given set of nodes"
    puts "    save   Save a disk image from a given node into a file"
    puts "    tell   Switch a given set of nodes ON/OFF or reboot them"
    puts "    stat   Returns the status of a given set of nodes"
    puts "  To get more help on individual commands: '#{@this_app} help [COMMAND]'"
    puts "  Examples:"
    puts "            #{@this_app} #{cmd} help exec   Return usage/help for the 'exec' command"
    puts "            #{@this_app} #{cmd} help load   Return usage/help for the 'load' command"
  end
end

# print help if no arguments are given
if ARGV.empty?
  help()
  exit 0
end

parseOptions()

# specify valid commands
commands = Array["exec","stat","tell","load","save","help"]

if commands.include?(ARGV[0])
  if (ARGV[0]=="help")
    help(ARGV[1])
  elsif @options[:help]
    help(ARGV[0])
  else
    run(ARGV[0])
  end
else
  puts "Unknown command: #{ARGV[0]}"
  help()
  exit 1
end
